#![cfg_attr(not(feature = "std"), no_std)]


use liquid::storage;
use liquid_lang::{InOut,State};
use liquid_lang as liquid;

#[liquid::contract]
mod ballot 
{
    use super::*;

    #[derive(Debug,InOut,State)]
    pub struct Proposal
    {
        name: String,
        content: String,
        vote_count: u32
    }

    #[derive(Debug,InOut,State)]
    pub struct Voter
    {
        weight: u32,
        voted: bool,
        vote: i32,
        delegate: address
    }

    /// Defines the state variables of your contract.
    #[liquid(storage)]
    struct Ballot 
    {
        chair_person: storage::Value<address>,
        proposals: storage::Vec<Proposal>,
        votable: storage::Mapping<address,Voter>
    }

    /// Defines the methods of your contract.
    #[liquid(methods)]
    impl Ballot {
        pub fn new(&mut self) 
        {
            self.chair_person.initialize(address{0:[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]});
            self.proposals.initialize();
            self.votable.initialize();
            let voter = Voter
            {
                weight:1,
                voted:false,
                vote:-1,
                delegate: address{0:[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
            };
            self.votable.insert(&self.chair_person,voter);
        }


        pub fn authentic(&mut self, addr:address, voter:Voter)
        {
            self.votable.insert(&addr, voter);
        }


        pub fn delegate(&mut self, _delegater:address,_delegatee:address)
        {
            let delegater = self.votable.get_mut(&_delegater).unwrap();
            require(delegater.voted == false, "Delegater has voted");
            let new_delegater =  Voter
            {
                weight: 0,
                voted: true,
                vote: -1,
                delegate: _delegatee,
            };

            let temp = delegater.weight;
            let delegatee = self.votable.get_mut(&_delegatee).unwrap();
            require(delegatee.voted == false, "Delegatee has voted");
            let new_delegatee = Voter
            {
                weight: delegatee.weight + temp,
                voted: false,
                vote: -1,
                delegate: delegatee.delegate
            };
            delegatee.weight += 1;
            self.votable.insert(&_delegater,new_delegater);
            self.votable.insert(&_delegatee,new_delegatee);
        }


        pub fn new_voter(&mut self, addr:address)
        {
            let voter = Voter
            {
                weight:1,
                voted:false,
                vote:-1,
                delegate: addr
            };
            self.authentic(addr, voter);
        }


        pub fn new_proposal(&mut self, name:String, content:String)
        {
            let proposal = Proposal
            {
                name: name,
                content: content,
                vote_count: 0
            };
            self.proposals.push(proposal);
        }


        pub fn vote(&mut self, proposal:u32, addr:address)
        {
            let voter = self.votable.get_mut(&addr).unwrap();
            require(voter.voted == false, "Voter has voted or delegate to other voter");
            self.proposals[proposal].vote_count += voter.weight;
            let new_voter = Voter
            {
                weight: voter.weight,
                voted: true,
                vote: proposal as i32,
                delegate: voter.delegate
            };
            self.votable.insert(&addr,new_voter);
        }

        
        pub fn get_winner_name(&self) -> String
        {
            let mut winner_vote_count = 0;
            let mut winner_vote_name = String::from("");
            for i in 0..self.proposals.len()
            {
                if self.proposals[i].vote_count > winner_vote_count
                {
                    winner_vote_count = self.proposals[i].vote_count;
                    winner_vote_name = self.proposals[i].name.clone();
                }
            }
            winner_vote_name
        }

        pub fn get_winner_content(&self) -> String
        {
            let mut winner_vote_count = 0;
            let mut winner_vote_content = String::from("");
            for i in 0..self.proposals.len()
            {
                if self.proposals[i].vote_count > winner_vote_count
                {
                    winner_vote_count = self.proposals[i].vote_count;
                    winner_vote_content = self.proposals[i].content.clone();
                }
            }
            winner_vote_content
        }
    }

    /// Unit tests in Rust are normally defined within such a `#[cfg(test)]`
    /// module and test functions are marked with a `#[test]` attribute.
    /// The below code is technically just normal Rust code.
    #[cfg(test)]
    mod tests {
        /// Imports all the definitions from the outer scope so we can use them here.
        use super::*;


        #[test]
        fn authentic_work()
        {
            let addr = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            let voter_1 = Voter
            {
                weight:1,
                voted:false,
                vote:-1,
                delegate: addr
            };
            let mut ballot_test = Ballot::new();
            ballot_test.authentic(addr,voter_1);
            assert_eq!(ballot_test.votable.get(&addr).unwrap().weight,1);
            assert_eq!(ballot_test.votable.get(&addr).unwrap().voted,false);
            assert_eq!(ballot_test.votable.get(&addr).unwrap().vote,-1);
            assert_eq!(ballot_test.votable.get(&addr).unwrap().delegate,addr);
        }


        #[test]
        fn vote_work()
        {
            let mut ballot_test = Ballot::new();
            let p1_name = String::from("proposal1");
            let p1_content = String::from("content1");
            ballot_test.new_proposal(p1_name,p1_content);

            let p2_name = String::from("proposal2");
            let p2_content = String::from("content2");
            ballot_test.new_proposal(p2_name,p2_content);

            let p3_name = String::from("proposal3");
            let p3_content = String::from("content3");
            ballot_test.new_proposal(p3_name,p3_content);


            let addr1 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            ballot_test.new_voter(addr1);

            let addr2 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]};
            ballot_test.new_voter(addr2);

            assert_eq!(ballot_test.proposals[0].vote_count, 0);
            assert_eq!(ballot_test.proposals[1].vote_count, 0);
            assert_eq!(ballot_test.proposals[2].vote_count, 0);

            ballot_test.vote(0,addr1);
            assert_eq!(ballot_test.proposals[0].vote_count, 1);
            assert_eq!(ballot_test.proposals[1].vote_count, 0);
            assert_eq!(ballot_test.proposals[2].vote_count, 0);
            assert_eq!(ballot_test.votable.get(&addr1).unwrap().voted,true);
            assert_eq!(ballot_test.votable.get(&addr1).unwrap().vote,0);

            ballot_test.vote(1,addr2);
            assert_eq!(ballot_test.proposals[0].vote_count, 1);
            assert_eq!(ballot_test.proposals[1].vote_count, 1);
            assert_eq!(ballot_test.proposals[2].vote_count, 0);
            assert_eq!(ballot_test.votable.get(&addr2).unwrap().voted,true);
            assert_eq!(ballot_test.votable.get(&addr2).unwrap().vote,1);

        }


        #[test]
        fn delegate_work()
        {
            let mut ballot_test = Ballot::new();

            let addr1 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            ballot_test.new_voter(addr1);

            let addr2 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]};
            ballot_test.new_voter(addr2);

            let addr3 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3]};
            ballot_test.new_voter(addr3);

            let voter1 = ballot_test.votable.get_mut(&addr1).unwrap();
            assert_eq!(voter1.weight, 1);
            assert_eq!(voter1.voted, false);
            assert_eq!(voter1.vote, -1);
            assert_eq!(voter1.delegate, addr1);
            let voter2 = ballot_test.votable.get_mut(&addr2).unwrap();
            assert_eq!(voter2.weight, 1);
            assert_eq!(voter2.voted, false);
            assert_eq!(voter2.vote, -1);
            assert_eq!(voter2.delegate, addr2);
            let voter3 = ballot_test.votable.get_mut(&addr3).unwrap();
            assert_eq!(voter3.weight, 1);
            assert_eq!(voter3.voted, false);
            assert_eq!(voter3.vote, -1);
            assert_eq!(voter3.delegate, addr3);

            ballot_test.delegate(addr1,addr2);
            let voter1 = ballot_test.votable.get_mut(&addr1).unwrap();
            assert_eq!(voter1.weight, 0);
            assert_eq!(voter1.voted, true);
            assert_eq!(voter1.vote, -1);
            assert_eq!(voter1.delegate, addr2);
            let voter2 = ballot_test.votable.get_mut(&addr2).unwrap();
            assert_eq!(voter2.weight, 2);
            assert_eq!(voter2.voted, false);
            assert_eq!(voter2.vote, -1);
            assert_eq!(voter2.delegate, addr2);
            let voter3 = ballot_test.votable.get_mut(&addr3).unwrap();
            assert_eq!(voter3.weight, 1);
            assert_eq!(voter3.voted, false);
            assert_eq!(voter3.vote, -1);
            assert_eq!(voter3.delegate, addr3);

            ballot_test.delegate(addr2,addr3);
            let voter1 = ballot_test.votable.get_mut(&addr1).unwrap();
            assert_eq!(voter1.weight, 0);
            assert_eq!(voter1.voted, true);
            assert_eq!(voter1.vote, -1);
            assert_eq!(voter1.delegate, addr2);
            let voter2 = ballot_test.votable.get_mut(&addr2).unwrap();
            assert_eq!(voter2.weight, 0);
            assert_eq!(voter2.voted, true);
            assert_eq!(voter2.vote, -1);
            assert_eq!(voter2.delegate, addr3);
            let voter3 = ballot_test.votable.get_mut(&addr3).unwrap();
            assert_eq!(voter3.weight, 3);
            assert_eq!(voter3.voted, false);
            assert_eq!(voter3.vote, -1);
            assert_eq!(voter3.delegate, addr3);

        }


        #[test]
        fn get_winner_name_work()
        {
            let mut ballot_test = Ballot::new();
            let p1_name = String::from("proposal1");
            let p1_content = String::from("content1");
            ballot_test.new_proposal(p1_name,p1_content);

            let p2_name = String::from("proposal2");
            let p2_content = String::from("content2");
            ballot_test.new_proposal(p2_name,p2_content);

            let p3_name = String::from("proposal3");
            let p3_content = String::from("content3");
            ballot_test.new_proposal(p3_name,p3_content);

            let addr1 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            ballot_test.new_voter(addr1);

            let addr2 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]};
            ballot_test.new_voter(addr2);

            let addr3 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3]};
            ballot_test.new_voter(addr3);

            assert_eq!(ballot_test.get_winner_name(), "");

            ballot_test.vote(0,addr1);
            assert_eq!(ballot_test.get_winner_name(), "proposal1");

            ballot_test.vote(1,addr2);
            assert_eq!(ballot_test.get_winner_name(), "proposal1");

            ballot_test.vote(1,addr3);
            assert_eq!(ballot_test.get_winner_name(), "proposal2");

        }

        #[test]
        fn get_winner_content_work()
        {
            let mut ballot_test = Ballot::new();
            let p1_name = String::from("proposal1");
            let p1_content = String::from("content1");
            ballot_test.new_proposal(p1_name,p1_content);

            let p2_name = String::from("proposal2");
            let p2_content = String::from("content2");
            ballot_test.new_proposal(p2_name,p2_content);

            let p3_name = String::from("proposal3");
            let p3_content = String::from("content3");
            ballot_test.new_proposal(p3_name,p3_content);

            let addr1 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]};
            ballot_test.new_voter(addr1);

            let addr2 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]};
            ballot_test.new_voter(addr2);

            let addr3 = address{0:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3]};
            ballot_test.new_voter(addr3);

            assert_eq!(ballot_test.get_winner_content(), "");

            ballot_test.vote(0,addr1);
            assert_eq!(ballot_test.get_winner_content(), "content1");

            ballot_test.vote(1,addr2);
            assert_eq!(ballot_test.get_winner_content(), "content1");

            ballot_test.vote(1,addr3);
            assert_eq!(ballot_test.get_winner_content(), "content2");

        }
    }
}